import { getAnalytics } from "@/lib/analytics/get-analytics";
import { getStartEndDates } from "@/lib/analytics/utils/get-start-end-dates";
import { DubApiError } from "@/lib/api/errors";
import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw";
import { withWorkspace } from "@/lib/auth";
import { throwIfNoPartnerIdOrTenantId } from "@/lib/partners/throw-if-no-partnerid-tenantid";
import { sqlGranularityMap } from "@/lib/planetscale/granularity";
import {
  partnerAnalyticsQuerySchema,
  partnersTopLinksSchema,
} from "@/lib/zod/schemas/partners";
import { prisma } from "@dub/prisma";
import { format } from "date-fns";
import { NextResponse } from "next/server";

// GET /api/partners/analytics – get analytics for a partner
export const GET = withWorkspace(
  async ({ workspace, searchParams }) => {
    const programId = getDefaultProgramIdOrThrow(workspace);

    const {
      groupBy,
      partnerId,
      tenantId,
      interval = "all",
      start,
      end,
      timezone,
      query,
    } = partnerAnalyticsQuerySchema.parse(searchParams);

    throwIfNoPartnerIdOrTenantId({ partnerId, tenantId });

    const programEnrollment = await prisma.programEnrollment.findUniqueOrThrow({
      where: partnerId
        ? {
            partnerId_programId: {
              partnerId,
              programId,
            },
          }
        : {
            tenantId_programId: {
              tenantId: tenantId!,
              programId,
            },
          },
      include: {
        program: true,
        links: {
          orderBy: {
            clicks: "desc",
          },
        },
      },
    });

    if (programEnrollment.program.workspaceId !== workspace.id) {
      throw new DubApiError({
        code: "not_found",
        message: "Program not found.",
      });
    }

    const analytics = await getAnalytics({
      event: "composite",
      groupBy,
      linkIds: programEnrollment.links.map((link) => link.id),
      interval,
      start,
      end,
      timezone,
      query,
    });

    const { startDate, endDate, granularity } = getStartEndDates({
      interval,
      start,
      end,
      timezone,
    });

    // Group by count
    if (groupBy === "count") {
      const earnings = await prisma.commission.aggregate({
        _sum: {
          earnings: true,
        },
        where: {
          type: "sale",
          amount: {
            gt: 0,
          },
          programId: programEnrollment.programId,
          partnerId: programEnrollment.partnerId,
          status: {
            in: ["pending", "processed", "paid"],
          },
          createdAt: {
            gte: startDate,
            lt: endDate,
          },
        },
      });

      return NextResponse.json({
        ...analytics,
        earnings: earnings._sum.earnings || 0,
      });
    }

    const { dateFormat } = sqlGranularityMap[granularity];

    // Group by timeseries
    if (groupBy === "timeseries") {
      const earnings = await prisma.$queryRaw<
        { start: string; earnings: number }[]
      >`
    SELECT 
      DATE_FORMAT(CONVERT_TZ(createdAt, '+00:00', ${timezone || "+00:00"}),  ${dateFormat}) AS start, 
      SUM(earnings) AS earnings
    FROM Commission
    WHERE 
      earnings > 0
      AND programId = ${programEnrollment.programId}
      AND partnerId = ${programEnrollment.partnerId}
      AND status in ('pending', 'processed', 'paid')
      AND type = 'sale'
      AND createdAt >= ${startDate}
      AND createdAt < ${endDate}
    GROUP BY start
    ORDER BY start ASC;`;

      const earningsLookup = Object.fromEntries(
        earnings.map((item) => [
          format(
            new Date(item.start),
            granularity === "hour"
              ? "yyyy-MM-dd'T'HH:00"
              : "yyyy-MM-dd'T'00:00",
          ),
          {
            earnings: item.earnings,
          },
        ]),
      );

      const analyticsWithRevenue = analytics.map((item) => {
        const formattedDateTime = format(
          new Date(item.start),
          granularity === "hour" ? "yyyy-MM-dd'T'HH:00" : "yyyy-MM-dd'T'00:00",
        );

        return {
          ...item,
          earnings: Number(earningsLookup[formattedDateTime]?.earnings ?? 0),
        };
      });

      return NextResponse.json(analyticsWithRevenue);
    }

    // Group by top_links
    const topLinkEarnings = await prisma.commission.groupBy({
      by: ["linkId"],
      where: {
        type: "sale",
        amount: {
          gt: 0,
        },
        programId: programEnrollment.programId,
        partnerId: programEnrollment.partnerId,
        status: {
          in: ["pending", "processed", "paid"],
        },
        createdAt: {
          gte: startDate,
          lt: endDate,
        },
      },
      _sum: {
        earnings: true,
      },
    });

    const topLinksWithEarnings = programEnrollment.links.map((link) => {
      const analyticsData = analytics.find((a) => a.id === link.id);
      const earnings = topLinkEarnings.find((t) => t.linkId === link.id);

      return partnersTopLinksSchema.parse({
        ...link,
        ...analyticsData,
        link: link.id,
        createdAt: link.createdAt.toISOString(),
        earnings: Number(earnings?._sum.earnings ?? 0),
      });
    });

    return NextResponse.json(topLinksWithEarnings);
  },
  {
    requiredPlan: ["advanced", "enterprise"],
  },
);
